(function (root, factory) {
  // debugger
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if (typeof define === 'function' && define.amd)
    define([], factory);
  else if (typeof exports === 'object')
    exports["DBFactory"] = factory();
  else
    root["DBFactory"] = factory();
})(window, function () {

  /**
   * indexedDB api
   */
  class DBFactory {
    constructor({ dbName, dbVersion, stores }) {
      this._db = null
      this.DB_NAME = dbName
      this.DB_VERSION = dbVersion
      this.isOpen = false
      this.stores = Object.prototype.toString.call(stores) === "[object Array]" ? stores : []
    }
    open() {
      return new Promise((resolve, reject) => {
        const request = indexedDB.open(this.DB_NAME, this.DB_VERSION)
        request.onsuccess = (e) => {
          this.isOpen = true

          Object.defineProperty(this, '_db', {
            writable: false,
            value: e.target.result
          })

          this._db.onclose = function () {
            this.isOpen = false
            console.log("已经关闭数据库: " + this.DB_NAME)
          }
          this.log('成功打开数据库: ' + this.DB_NAME)
          resolve()
        }
        request.onerror = (e) => {
          let error = e.target.error
          reject({
            name: error.name,
            code: error.code,
            errMsg: error.message
          })
        }
        request.onupgradeneeded = this._onUpgradeneeded.bind(this)
      })
    }
    /**
     * 关闭数据库
     */
    close() {
      this.isOpen = false
      this._db.close()
    }
    /**
     * 删除数据库
     */
    deleteDatabase() {
      return new Promise((resolve, reject) => {
        if (this.isOpen) {
          this.close()
        }
        let request = indexedDB.deleteDatabase(this.DB_NAME)
        request.onsuccess = function (e) {
          resolve()
        }
        request.onerror = request.onabort = function (e) {
          let error = e.target.error
          reject({
            name: error.name,
            code: error.code,
            errMsg: error.message
          })
        }
      })
    }
    /**
     * 添加数据 主键重复会报错
     * @param {*} storeName 
     * @param {*} data 
     */
    addData(storeName, data) {
      return this._requestDBResult(storeName, "readwrite", function (store) {
        return store.add(data)
      })
    }
    /**
     * 更新 主键重复会更新
     * @param {*} storeName 
     * @param {*} newData 
     */
    putData(storeName, newData) {
      return this._requestDBResult(storeName, "readwrite", function (store) {
        // debugger keyPath
        return store.put(newData)
      })
    }
    /**
     * 更新 主键重复会更新
     * @param {*} storeName 
     * @param {*} data 
     */
    putDatas(storeName, datas, isAdd) {
      return new Promise((resolve, reject) => {
        let store = this._getObjectStore(storeName, "readwrite")
        let i = 0
        let all = []
        let errors = []
        let len = datas.length
        let errorCount = 0
        datas.forEach(data => {
          let temp = i
          i++
          let req = isAdd ? store.add(data) : store.put(data)
          req.onsuccess = function (e) {
            all[temp] = e.target.result
            if ((all.length + errorCount) === len) {
              resolve({
                data: all,
                success: all.length,
                errors: errors,
                errorCount: errorCount
              })
            }
          }
          req.onerror = function () {
            errorCount++
            errors.push(e.target.error)
            if ((all.length + errorCount) === len) {
              resolve({
                data: all,
                success: all.length,
                errors: errors,
                errorCount: errorCount
              })
            }
          }
        })

      })
    }
    /**
     * 根据主键查找数据
     * @param {store name} storeName 
     * @param {要查找的值} keyPathValue 
     */
    getDataByKey(storeName, keyPathValue) {
      return this._requestDBResult(storeName, "readonly", function (store) {
        return store.get(keyPathValue)
      })
    }
    /**
     * 根据主键删除数据
     * @param {*} storeName 
     * @param {*} keyPathValue 
     */
    deleteDataByKey(storeName, keyPathValue) {
      return this._requestDBResult(storeName, "readwrite", function (store) {
        return store.delete(keyPathValue)
      })
    }
    /**
     * 
     * @param {*} storeName 
     * @param {*} option 
     * option = {
     *  start:      
     *  lowerOpen:  // 查询范围 是否 不包含 start 默认 false
     *  end: 
     *  upperOpen:  // 查询范围 是否 不包含 end   默认 true
     *  index:      // 索引名 若为空，则为根据主键查询
     *  indexValue: // 索引值 start end存在时不生效 
     * }
     */
    getStoreData(storeName, option) {
      return this._requestDBCursor(storeName, function (store) {
        let keyRange = option ? this._getIDBKeyRange(option) : null
        // console.log(keyRange, "keyRange")
        option = option || {}
        if (option.index && store.keyPath !== option.index) {
          let indexStore = store.index(option.index)
          return indexStore.openCursor(keyRange, option.reverse ? (IDBCursor.PREV || "prev") : undefined)
        }
        if (keyRange === null && store.getAll) {
          return store.getAll()
        }
        else {
          return store.openCursor(keyRange, option.reverse ? (IDBCursor.PREV || "prev") : undefined)
        }
      })
    }
    /**
     * 获取总条数
     * @param {*} storeName 
     */
    getStoreCount(storeName) {
      return this._requestDBResult(storeName, "readonly", function (store) {
        return store.count()
      })
    }
    getCountByIndex(storeName, index, indexValue) {
      return this._requestDBResult(storeName, "readonly", function (store) {
        if (index && store.keyPath !== index) {
          let indexStore = store.index(index)
          return indexStore.count(indexValue)
        }
        else {
          return store.count(indexValue || null)
        }

      })
    }
    /**
     * 根据 索引获取数据
     * @param {*} storeName 
     * @param {* 索引名} index 
     * @param {* 索引值} indexValue 
     * 获取到的是最早储存的一条数据
     */
    getDataByIndexOnly(storeName, index, indexValue) {
      return this._requestDBResult(storeName, "readonly", function (store) {
        const indexStore = store.index(index)
        return indexStore.get(indexValue)
      })
    }
    deleteDataByIndex() {
      return Promise.reject({
        code: 0,
        name: "DeleteDataByIndexError",
        errMsg: "按照索引删除未实现"
      })
    }
    /**
     * 清除store所有数据
     * @param {*} storeName 
     */
    clearStore(storeName) {
      return this._requestDBResult(storeName, "readwrite", function (store) {
        return store.clear()
      })
    }
    _requestDBResult(storeName, writeMode, next) {
      return this._Promise((resolve, reject) => {
        const store = this._getObjectStore(storeName, writeMode)
        const req = next(store)
        req.onsuccess = (e) => {
          resolve(e.target.result)
          this.log("[addData] store objectStore onsuccess, storeName: " + storeName)
        }
        req.onerror = (e) => {
          let error = e.target.error
          reject({
            name: error.name,
            code: error.code,
            errMsg: error.message
          })
          this.log("[addData] store objectStore onerror, storeName: " + storeName, e.target.error)
        }
      })
    }
    _getIDBKeyRange(option) {
      if (!option) {
        return null
      }
      option.lowerOpen = "lowerOpen" in option ? option.lowerOpen : false
      option.upperOpen = "upperOpen" in option ? option.upperOpen : true
      if (option.start && option.end) {
        // 当前索引的值介于 start 和 end 之间
        return IDBKeyRange.bound(option.start, option.end, option.lowerOpen, option.upperOpen);
      }
      else if (option.start) {
        // 当前索引的值 大于 start
        return IDBKeyRange.lowerBound(option.start, option.lowerOpen);
      }
      else if (option.end) {
        // 当前索引的值 小于 start
        return IDBKeyRange.upperBound(option.end, option.upperOpen);
      }
      else if (option.indexValue) {
        // 当前索引的值 等于 start
        return IDBKeyRange.only(option.indexValue);
      }
      else {
        return null
      }
    }

    _requestDBCursor(storeName, next, mode) {
      return this._Promise((resolve, reject) => {
        let data = []
        const store = this._getObjectStore(storeName, mode || 'readonly')
        const curRequest = next.call(this, store)
        if (!(curRequest instanceof IDBRequest)) {
          resolve(curRequest)
          return
        }

        curRequest.onsuccess = (e) => {
          let cursor = e.target.result
          // console.log(cursor)
          // debugger
          if (cursor) {
            if (!(cursor instanceof IDBCursorWithValue)) {
              resolve(cursor)
              return
            }
            cursor.continue()
            data.push(cursor.value)
            this.log("[getTabData] request onsuccess, storeName: " + storeName + " curKey: " + cursor.key)
          }
          else {
            resolve(data)
          }

        }
        curRequest.onerror = (e) => {
          let error = e.target.error
          reject({
            name: error.name,
            code: error.code,
            errMsg: error.message
          })
          this.log("[getTabData] request onerror, storeName: " + storeName, e.target.error)
        }
      })
    }
    _getObjectStore(storeName, mode) {
      if (!this._db.objectStoreNames.contains(storeName)) {
        this.log("[_getObjectStore] store 不存在：storeName: ", storeName)
        throw new Error(`${storeName} does not exist`)
        return null
      }
      //  mode: readonly, readewrite, versionchange
      const transaction = this._db.transaction(storeName, mode)
      transaction.oncomplete = (e) => {
        this.log("[_getObjectStore] transaction complete, storeName: " + storeName, e)
      }
      transaction.onabort = (e) => {
        this.log("[_getObjectStore] transaction abort, storeName: " + storeName, e)
      }
      transaction.onerror = (e) => {
        this.log("[_getObjectStore] transaction error, storeName: " + storeName, e.target.error)
      }
      return transaction.objectStore(storeName)
    }
    _onUpgradeneeded(e) {
      const db = e.target.result
      const transaction = e.target.transaction
      // Object.defineProperty(this, '_db', {
      //   writable: false,
      //   value: e.target.result
      // })
      this.log('升级数据库')
      const createStore = onCreateStore.bind(this, db, transaction)
      this.stores.forEach(createStore)

      function onCreateStore(db, transaction, store) {
        if (db.objectStoreNames.contains(store.storeName)) {
          // debugger
          // db.deleteObjectStore(store.storeName)
          this.log('[onCreateStore]' + store.storeName + '已存在')
          const store = transaction.objectStore(store.storeName)
          let indexNames = Array.from(store.indexNames)
          for (let i = 0, len = store.indexs.length; i < len; i++) {
            let index = store.indexs[i]
            let post = indexNames.indexOf(index.indexName)
            if (post === -1) {
              store.createIndex(index.indexName, index.indexKey || index.indexName, {
                unique: index.unique || false
              })
            }
            indexNames.splice(post, 1)
            // console.log(index)
          }
          indexNames.forEach(item => {
            store.deleteIndex(item)
          })
          // console.log("indexNamesindexNamesindexNames", indexNames)
          return
        }
        // else {
        const keyPath = store.keyPath
        const indexs = store.indexs
        const objectStore = db.createObjectStore(store.storeName, {
          keyPath: keyPath.keyPathName,
          autoIncrement: 'autoIncrement' in keyPath ? keyPath.autoIncrement : false
        })
        indexs.forEach(index => {
          // 三个参数，第一个是索引名称，第二个是建立索引的属性名，第三个是参数对象 TODO
          objectStore.createIndex(index.indexName, index.indexKey || index.indexName, {
            unique: index.unique || false
          })
        })
      }
    }
    _Promise(fun) {
      // debugger
      return new Promise((resolve, reject) => {
        if (!this.isOpen) {

          reject({
            code: 0,
            name: "OpenError",
            errMsg: "db is not open"
          })
          return
        }
        fun(resolve, reject)
      })
    }
    log(str) {
      // console.log("[ " + this.DB_NAME + " ] " + str)
      // console.log(...l)
    }
  }
  DBFactory.deleteDatabase = function (dbName) {
    indexedDB.deleteDatabase(dbName)
  }
  return DBFactory
})
